/*
 *  Copyright 2013 National Institute of Advanced Industrial Science and Technology
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.sss.mapreduce;

import java.io.Serializable;
import java.util.UUID;

/**
 * <code>GroupID</code> represents a data set in a storage system.
 */
@SuppressWarnings("serial")
public class GroupID implements Serializable {
  private static int UUID_STRING_LENGTH = (new UUID(0L, 0L)).toString().length();
  private static int DEFAULT_NSLOT = 16;
  private final boolean   broadcast;
  private final boolean   destructive;
  private final KeyFormat keyformat;
  private final UUID      uuid;
  private final int       nSlot;

  /**
   * .
   * @see #createFromString
   * @see #createRandom
   */
  private GroupID(KeyFormat keyformat, UUID uuid, boolean broadcast, boolean destructive, int nSlot) {
    if ((nSlot & -nSlot) != nSlot || nSlot > 32 || nSlot <= 0) {
      throw new IllegalArgumentException("Invalid nSlot: " + nSlot);
    }
    this.keyformat   = keyformat;
    this.uuid        = uuid;
    this.broadcast   = broadcast;
    this.destructive = destructive;
    this.nSlot     = nSlot;
  }

  /**
   * Returns the string representation for this <code>GroupID</code>.
   *
   * <p>
   * A string representation of a <code>GroupID</code> should match the following
   * regular expression:
   * <blockquote><pre>
   *   "b?d?[012][:xdigit:]{8}-[:xdigit:]{4}-[:xdigit:]{4}-[:xdigit:]{4}-[:xdigit:]{12}(/[:digit:]+)?"
   * </pre></blockquote>
   * <ul>
   * <li><tt>"b?"</tt>  :  indicates broadcast attribute<ul>
   *     <li><tt>"b"</tt> for broadcast attribute is true
   *     <li><tt>""</tt> for false.</ul>
   * <li><tt>"d?"</tt>  :  indicates destructive attribute<ul>
   *     <li><tt>"d"</tt> for destructive attribute is true
   *     <li><tt>""</tt> for false.</ul>
   * <li><tt>"012"</tt> :  indicates key formatter<ul>
   *     <li><tt>"0"</tt> for <code>KeyFormat.ORDER</code>
   *     <li><tt>"1"</tt> for <code>KeyFormat.THREAD</code>
   *     <li><tt>"2"</tt> for <code>KeyFormat.USERKEY</code></ul>
   * <li><tt>"[:xdigit:]{8}-[:xdigit:]{4}-[:xdigit:]{4}-[:xdigit:]{4}-[:xdigit:]{12}"</tt><p>
   *     the string representation for internal <code>UUID</code>.</p>
   * <li><tt>(_[:digit:]+)a</tt> : number of slot.
   * </ul>
   */
  @Override
  public String toString() {
    String slotStr = "";
    if (nSlot != DEFAULT_NSLOT) {
      slotStr = "_" + nSlot;
    }
    return (broadcast?"b":"") + (destructive?"d":"") + keyformat.getChar() + uuid.toString() + slotStr;
  }

  /**
   * Create a new <code>GroupID</code> object.
   *
   * @param  keyformat    the key format for this <code>GroupID</code>
   * @param  m            most significant 64 bits of the <code>UUID</code>
   * @param  l            least significant 64 bits of the <code>UUID</code>
   * @param  broadcast    true - tuples are broadcasted, otherwise normal operation.
   * @param  destructive  true - tuples will be removed (destructive read mode)
   * @param  nSlot        number of slot
   */
  public static GroupID create(KeyFormat keyformat, long m, long l, boolean broadcast, boolean destructive, int nSlot) {
    return new GroupID(keyformat, new UUID(m, l), broadcast, destructive, nSlot);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   keyformat  key format
   * @param   broadcast  broadcast or not
   * @param   destructive  true - tuples will be removed (destructive read mode)
   * @param   nSlot      number of slot
   * @return  a new <code>GroupID</code> object
   * @see     org.sss.mapreduce.KeyFormat
   */
  public static GroupID createRandom(KeyFormat keyformat, boolean broadcast, boolean destructive, int nSlot) throws SssException {
    return new GroupID(keyformat, UUID.randomUUID(), broadcast, destructive, nSlot);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   keyformat  key format
   * @param   broadcast  broadcast or not
   * @param   destructive  true - tuples will be removed (destructive read mode)
   * @return  a new <code>GroupID</code> object
   * @see     org.sss.mapreduce.KeyFormat
   */
  public static GroupID createRandom(KeyFormat keyformat, boolean broadcast, boolean destructive) throws SssException {
    return createRandom(keyformat, broadcast, destructive, DEFAULT_NSLOT);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   keyformat  key format
   * @return  a new <code>GroupID</code> object
   * @see     org.sss.mapreduce.KeyFormat
   */
  public static GroupID createRandom(KeyFormat keyformat, boolean broadcast) throws SssException {
    return createRandom(keyformat, broadcast, false);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   keyformat  key format
   * @see     org.sss.mapreduce.KeyFormat
   */
  public static GroupID createRandom(KeyFormat keyformat) throws SssException {
    return createRandom(keyformat, false);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   confOwner configuration owner
   * @param   broadcast broadcast or not.
   * @param   destructive  true - tuples will be removed (destructive read mode)
   * @return  a new <code>GroupID</code> object
   */
  public static GroupID createRandom(ConfigurationOwner confOwner, boolean broadcast, boolean destructive) throws SssException {
    return createRandom(
        confOwner.getConfiguration().getKeyFormat("job.new_group.keyformat"), broadcast,
        destructive, confOwner.getConfiguration().getInt("job.new_group.numslot", 16));
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   confOwner configuration owner
   * @param   broadcast broadcast or not.
   * @return  a new <code>GroupID</code> object
   */
  public static GroupID createRandom(ConfigurationOwner confOwner, boolean broadcast) throws SssException {
    return createRandom(confOwner, broadcast, false);
  }

  /**
   * Greate a new <code>GroupID</code> object that has random UUID.
   *
   * @param   confOwner configuration owner
   * @return  a new <code>GroupID</code> object
   */
  public static GroupID createRandom(ConfigurationOwner confOwner) throws SssException {
    return createRandom(confOwner, false);
  }

  /**
   * Create a <code>GroupID</code> object from the given string representation.
   *
   * @param   str  string representation for <code>GroupID</code> to be created.
   * @return  a new <code>GroupID</code> object
   */
  public static GroupID createFromString(String str) throws IllegalArgumentException {
    // TODO:
    str = str.trim();
    int idx = 0;
    boolean broadcast = false;
    if (str.charAt(idx) == 'b') {
      idx++;
      broadcast = true;
    }
    boolean destructive = false;
    if (str.charAt(idx) == 'd') {
      idx++;
      destructive = true;
    }

    KeyFormat keyformat = null;
    for (KeyFormat kf: KeyFormat.values()) {
      if (str.charAt(idx) == kf.getChar()) {
        keyformat = kf;
        break;
      }
    }
    if (keyformat == null) {
      throw new IllegalArgumentException("Invalid keyformat: "  + str.charAt(0));
    }
    idx++;

    UUID uuid = UUID.fromString(str.substring(idx, idx + UUID_STRING_LENGTH));
    idx += UUID_STRING_LENGTH;

    int nSlot = DEFAULT_NSLOT;
    if (str.length() > idx) {
      if (str.charAt(idx) != '_') {
        throw new IllegalArgumentException("Invalid GroupID string");
      }
      nSlot = Integer.parseInt(str.substring(idx+1));
    }

    return new GroupID(keyformat, uuid, broadcast, destructive, nSlot);
  }

  /**
   * Get a non-volatile copy of a <code>GroupID</code>.
   *
   * @return  non-volatile version of this <code>GroupID</code>
   */
  public GroupID getNonVolatileCopy() {
    return new GroupID(keyformat, uuid, broadcast, destructive, nSlot) {
      @Override
      public boolean isDestructive() {
        return false;
      }
    };
  }

  /**
   * Returns a hash code value for this object.
   *
   * @return  a hash code value for this object.
   * @see     java.lang.Object#hashCode()
   */
  @Override
  public int hashCode(){
    // TODO: does not use keyformat. it is no problem?
    return uuid.hashCode();
  }
  /**
   * Indicates whether some other object is "equal to" this one.
   *
   * @param   that  the reference object with which to compare.
   * @return  <code>true</code> if this object is the same as the that argument;
   *          <code>false</code> otherwise.
   * @see     org.sss.mapreduce.KeyFormat
   * @see     java.util.UUID
   */
  @Override
  public boolean equals(Object that) {
    if (that == null) {
      return false;
    }
    if (!(that instanceof GroupID)) {
      return false;
    }
    GroupID o = (GroupID)that;
    return keyformat == o.keyformat && uuid.equals(o.uuid) && nSlot == o.nSlot;
  }

  /**
   * Returns a key format of this <code>GroupID</code>
   *
   * @return  a key format for this <code>GroupID</code>
   * @see     org.sss.mapreduce.KeyFormat
   */
  public KeyFormat getKeyFormat() {
    return keyformat;
  }
  /**
   * Returns the most significant 64 bits of this <code>GroupID</code>'s
   * <code>UUID</code>'s 128 bit value.
   *
   * @return the most significant 64 bits of this <code>GroupID</code>'s
   * <code>UUID</code>'s 128 bit value.
   * @see    java.util.UUID
   */
  public long getMostSignificantBits() {
    return uuid.getMostSignificantBits();
  }
  /**
   * Returns the least significant 64 bits of this <code>GroupID</code>'s
   * <code>UUID</code>'s 128 bit value.
   *
   * @return the least significant 64 bits of this <code>GroupID</code>'s
   * <code>UUID</code>'s 128 bit value.
   * @see    java.util.UUID
   */
  public long getLeastSignificantBits() {
    return uuid.getLeastSignificantBits();
  }
  /**
   * Indicates whether this <code>GroupID</code>'s broadcast attribute is
   * set or not.
   *
   * @return  <code>true</code>  if this object's broadcast attribute is set;
   *          <code>false</code> otherwise.
   */
  public boolean isBroadcast() {
    return broadcast;
  }
  /**
   * Indicates whether this <code>GroupID</code>'s destructive attribute is
   * set or not.
   *
   * @return  <code>true</code>  if this object's destructive attributes is set:
   *          <code>false</code> otherwise.
   */
  public boolean isDestructive() {
    return destructive;
  }

  /**
   * Returns number of slot of this <code>GroupID</code>.
   */
  public int getNSlot() {
    return nSlot;
  }
}
